﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using BibControles;
using System.ComponentModel;

namespace PostProcessing
{
    class Conviction
    {
        StreamReader readerRegras;
        StreamWriter writer;
        string regraConcatenada;

        /// <summary>
        /// Gera arquivo de regras contendo as medidas de interesse de acordo com o método Convicção.
        /// </summary>
        /// <param name="arqRegras">Arquivo contendo as regras.</param>
        /// <param name="arqDados">Base de dados.</param>
        /// <param name="nomeArqSaida">Nome do arquivo a ser gerado.</param>
        public void gerarGrausDeInteresse(string nomeArqRegras, string nomeArqDados, string nomeArqSaida, BackgroundWorker worker)
        {
            Dictionary<string,float> conseqsSups;
            List<string> blocoRegras;
            Rule regraDesmembrada;
            float suporteCons = 0;
            float suporteAnte = 0;
            float suporteRegra = 0;
            float grauInteresse = 0;
            int posicaoAtualProcessamento = 0;

            string grauRegra = "";
            string regsEscrever = "";
            char[] writeBuffer;

            conseqsSups = suporteConseqs(retornaConsequentes(nomeArqRegras, worker, false), nomeArqDados, worker);

            readerRegras = new StreamReader(nomeArqRegras);
            writer = new StreamWriter(nomeArqSaida);

            while (!readerRegras.EndOfStream)
            {
                blocoRegras = lerBlocoDados(1000000, readerRegras);

                //Processa as 1000000 de regras lidas.
                for (int i = 0; i < blocoRegras.Count; i++)
                {
                    regraConcatenada = blocoRegras[i].ToLower();
                    regraDesmembrada = Rule.desmembraRegra(clearSpaces(regraConcatenada));

                    suporteRegra = (regraDesmembrada.confianca / 100) * (regraDesmembrada.suporte / 100);
                    suporteAnte = regraDesmembrada.suporte / 100;
                    suporteCons = conseqsSups[regraDesmembrada.consequente];

                    if (suporteAnte == suporteRegra)
                        grauInteresse = float.PositiveInfinity;
                    else if ((suporteCons == 1.0f) || (suporteAnte == 0.0f))
                        grauInteresse = 0.0f;
                    else
                        grauInteresse = (suporteAnte * (1.0f - suporteCons)) / (suporteAnte - suporteRegra);

                    grauRegra = grauInteresse.ToString() + " " + regraConcatenada;
                    regsEscrever += grauRegra + "\r\n";

                    posicaoAtualProcessamento += blocoRegras[i].Length;
                    worker.ReportProgress((int)((posicaoAtualProcessamento * 100L) / readerRegras.BaseStream.Length));
                }

                //Escreve as 1000000 de regras processadas.
                writeBuffer = regsEscrever.ToCharArray();
                writer.Write(writeBuffer, 0, writeBuffer.Length);
                regsEscrever = "";

                blocoRegras.Clear();
            }
            readerRegras.Close();

            writer.Close();
        }

        public static List<string> retornaConsequentes(string nomeArqRegras, BackgroundWorker worker, bool regrasComGrau)
        {
            StreamReader readerRegras = new StreamReader(nomeArqRegras);
            List<string> blocoRegistros;
            string conseqAtual = "";
            List<string> consequentes = new List<string>();
            bool jaInserido = false;
            int indexConseq = regrasComGrau ? 1 : 0;

            while (!readerRegras.EndOfStream)
            {
                blocoRegistros = lerBlocoDados(1000000, readerRegras);

                foreach (string registro in blocoRegistros)
                {
                    conseqAtual = registro.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries)[indexConseq];

                    //Checa se o consequente lido já não foi lido anteriormente.
                    foreach (string conseq in consequentes)
                    {
                        if (conseq == conseqAtual)
                            jaInserido = true;
                    }

                    //Se consequente lido ainda não foi adicionado na lista, adiciona.
                    if (jaInserido == false)
                        consequentes.Add(conseqAtual);
                    else
                        jaInserido = false;
                }
                blocoRegistros.Clear();
                worker.ReportProgress((int)((readerRegras.BaseStream.Position * 100L) / readerRegras.BaseStream.Length));
            }
            readerRegras.Close();

            return consequentes;
        }

        public static List<string> lerBlocoDados(long qtdBytes, StreamReader readerDados)
        {
            char[] buffer = null;
            string blocoDados = "";
            long posicaoAtualArq;

            //Lê o bloco de dados.
            if ((readerDados != null) && !readerDados.EndOfStream)
            {
                if (readerDados.BaseStream.Position == 1024L || readerDados.BaseStream.Length <= 1024)
                    posicaoAtualArq = 0;
                else
                    posicaoAtualArq = readerDados.BaseStream.Position;

                if ((posicaoAtualArq + qtdBytes) > readerDados.BaseStream.Length)
                    blocoDados = readerDados.ReadToEnd().ToLower();
                else
                {
                    buffer = new char[qtdBytes];
                    readerDados.ReadBlock(buffer, 0, (int)qtdBytes);
                }
            }

            //Se foi lido alguma coisa, acrescenta até a próxima quebra de linha.
            if ((buffer != null) && (buffer.Length > 0))
            {
                char letraAtual;

                blocoDados = new string(buffer).ToLower();
                while (!readerDados.EndOfStream)
                {
                    letraAtual = (char) readerDados.Read();

                    if (letraAtual != '\r')
                        blocoDados += letraAtual;
                    else
                    {
                        blocoDados += letraAtual;   //Adiciona o '\r'.
                        blocoDados += (char) readerDados.Read();   //Adiciona o '\n'.
                        break;
                    }
                }
            }

            return new List<string>(blocoDados.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries));
        }

        /// <summary>
        /// Calcula o suporte de vários consequentes.
        /// </summary>
        /// <param name="consequentes"></param>
        /// <returns></returns>
        public Dictionary<string, float> suporteConseqs(List<string> consequentes, string nomeArqDados, BackgroundWorker worker)
        {
            Dictionary<string, float> conseqsSups = new Dictionary<string, float>(consequentes.Count);
            StreamReader readerDados = new StreamReader(nomeArqDados);
            List<string> blocoDados;
            int qtdRegs = 0;
            List<int> contadores = new List<int>(consequentes.Count);

            //Atribui zero a todos os contadores.
            for (int i = 0; i < consequentes.Count; i++)
            {
                contadores.Add(0);
            }

            //Adiciona todos os consequentes no dicionário.
            foreach (string conseq in consequentes)
            {
                conseqsSups.Add(conseq, 0);
            }

            //Enquanto não for fim do arquivo de dados...
            while (!readerDados.EndOfStream)
            {
                blocoDados = lerBlocoDados(1000000, readerDados);

                //Para cada registro do bloco de dados lido...
                foreach (string registro in blocoDados)
                {
                    //Para cada item do registro atual...
                    foreach (string regItem in registro.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries))
                    {
                        int index;

                        //Verifica se é um dos consequentes e se for incrementa o respectivo contador.
                        if ((index = consequentes.IndexOf(regItem)) >= 0)
                            contadores[index]++;
                    }
                    qtdRegs++;
                }
                blocoDados.Clear();
                worker.ReportProgress((int)((readerDados.BaseStream.Position * 100L) / readerDados.BaseStream.Length));
            }
            readerDados.Close();

            //Calcula o suporte de cada consequente.
            for (int i = 0; i < consequentes.Count; i++)
            {
                conseqsSups[consequentes[i]] = (float) contadores[i] / qtdRegs;
            }
            contadores.Clear();

            return conseqsSups;
        }

        /// <summary>
        /// Elimina as regras que obtiveram um baixo grau de interesse, onde todos os valores abaixo são eliminados.
        /// </summary>
        /// <param name="nomeArqRegras">Nome do Arquivo de Regras</param>
        /// <param name="limearCorte">Limear de corte para Regras</param>
        public int podarConjuntoDeRegras(string nomeArqRegras, float limearCorte, BackgroundWorker worker)
        {
            StreamReader streamReader = new StreamReader(nomeArqRegras);
            StreamWriter streamWriter = new StreamWriter(nomeArqRegras.Replace(".txt", "_podado.txt"));
            string[] registroDesmembrado;
            float grauInteresse;
            List<string> blocoRegras;
            string regrasEscrever = "";

            int qtdRegrasPodadas = 0;

             //Enquanto não for o fim do arquivo de regras...
            while (!streamReader.EndOfStream)
            {
                blocoRegras = lerBlocoDados(1000000, streamReader);

                foreach (string regraAtual in blocoRegras)
                {
                    registroDesmembrado = regraAtual.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    grauInteresse = float.Parse(registroDesmembrado[0].Replace("infinito", "Infinito"));

                    if (grauInteresse > limearCorte)
                        regrasEscrever += regraAtual + "\r\n";
                    else
                        qtdRegrasPodadas++;
                }
                blocoRegras.Clear();
                streamWriter.Write(regrasEscrever);
                regrasEscrever = "";

                worker.ReportProgress((int)((streamReader.BaseStream.Position * 100L) / streamReader.BaseStream.Length));
            }
            streamReader.Close();
            streamWriter.Close();

            return qtdRegrasPodadas;
        }

        private string clearSpaces(string linha)
        {
            string linhaLimpa = linha.Replace("  ", " ");

            if (linha != linhaLimpa)
                return clearSpaces(linhaLimpa);

            return linhaLimpa;
        }
    }
}
